#include "common.h"
#include <stdlib.h>
#include <time.h>
#include "burst.h"
#include "cpu/reg.h"
#include "nemu.h"

#define CACHE_BLOCK 64
#define CACHE_SIZE_L1 64*1024
#define CACHE_SIZE_L2 4*1024*1024
#define SET_WAY_L1 8
#define SET_WAY_L2 16
#define NUM_L1_BLOCK CACHE_SIZE_L1/CACHE_BLOCK
#define NUM_L2_BLOCK CACHE_SIZE_L2/CACHE_BLOCK

void init_cache();
void cache_l2_write(hwaddr_t, size_t, uint32_t);
uint32_t cache_l2_read(hwaddr_t);
void dram_write(hwaddr_t, size_t, uint32_t);
void ddr3_read_cache(hwaddr_t addr, void *data);
void ddr3_write_cache(hwaddr_t addr, void *data, uint8_t *mask);
void dram_write(hwaddr_t, size_t, uint32_t);
hwaddr_t page_translate(lnaddr_t addr);

uint64_t runtime = 0;

struct L1{
	bool valid;
	uint32_t tag;
	uint8_t data[CACHE_BLOCK];
} cache_l1[NUM_L1_BLOCK];

struct L2{
	bool valid,dirty;
	uint32_t tag;
	uint8_t data[CACHE_BLOCK];
} cache_l2[NUM_L2_BLOCK];

void init_cache(){
	int i;
	for(i=0;i<NUM_L1_BLOCK;i++){
		cache_l1[i].valid = 0;
		cache_l1[i].tag = 0;
		memset(cache_l1[i].data,0,CACHE_BLOCK);
	}
	for(i=0;i<NUM_L2_BLOCK;i++){
		cache_l2[i].valid = 0;
		cache_l2[i].dirty = 0;
		cache_l2[i].tag = 0;
		memset(cache_l2[i].data,0,CACHE_BLOCK);
	}
}

uint32_t cache_read(hwaddr_t addr){
	uint32_t group_num = (addr >> 6) & 0x7f;
	uint32_t dram_tag = addr >> 13;
	int i;
	bool hitflag = false;
	for (i = group_num * SET_WAY_L1 ; i < (group_num + 1) * SET_WAY_L1 ; i ++){
		if (cache_l1[i].tag == dram_tag && cache_l1[i].valid){
				hitflag = true;
				runtime += 2;
				break;
		}
	}
	if (!hitflag){
		runtime += 200;
		int j = cache_l2_read(addr);
		for (i = group_num * SET_WAY_L1 ; i < (group_num + 1) * SET_WAY_L1 ; i ++)
			if (!cache_l1[i].valid)	break;
		if(i == (group_num + 1) * SET_WAY_L1){
		srand(time(0));
		i = group_num * SET_WAY_L1 + rand() % SET_WAY_L1;
		}
		cache_l1[i].valid = true;
		cache_l1[i].tag = dram_tag;
		memcpy(cache_l1[i].data, cache_l2[j].data, CACHE_BLOCK);
	}
	return i;
}

uint32_t cache_l2_read(hwaddr_t addr){
	uint32_t group_num2 = (addr >> 6) & 0xfff;
	uint32_t block = (addr >> 6) << 6;
	uint32_t dram_tag2 = addr >> 18;
	int i;
	bool hitflag = false;
	for (i = group_num2 * SET_WAY_L2 ; i < (group_num2 + 1) * SET_WAY_L2; i ++){
		if (cache_l2[i].tag == dram_tag2 && cache_l2[i].valid){
			hitflag = true;
			runtime += 2;
			break;
		}
	}
	if (!hitflag){
		runtime += 200;
		int j;
		for (i = group_num2 * SET_WAY_L2 ; i < (group_num2 + 1) * SET_WAY_L2; i ++)
			if (!cache_l2[i].valid)	break;
		if (i == (group_num2 + 1) * SET_WAY_L2){
			srand(time(0));
			i = group_num2 * SET_WAY_L2 + rand() % SET_WAY_L2;
			if(cache_l2[i].dirty){
				uint8_t mask[BURST_LEN * 2];
				memset(mask, 1, BURST_LEN * 2);
				for (j = 0;j < CACHE_BLOCK/BURST_LEN;j ++)
				ddr3_write_cache(block + j * BURST_LEN, cache_l2[i].data + j * BURST_LEN, mask);
			}
		}
		cache_l2[i].valid = true;
		cache_l2[i].tag = dram_tag2;
		cache_l2[i].dirty = false;
		for (j = 0; j < BURST_LEN; j ++)
		ddr3_read_cache(block + j * BURST_LEN, cache_l2[i].data + j * BURST_LEN);
	}
	return i;
}

void cache_write(hwaddr_t addr, size_t len, uint32_t data) {
	uint32_t group_num = (addr >> 6) & 0x7f; 
	uint32_t offset = addr & 0x3f;
	uint32_t dram_tag = addr >> 13; 
	int i;
	bool hitflag = false;
	for(i = group_num * SET_WAY_L1 ; i < (group_num + 1) * SET_WAY_L1 ; i ++){
		if(cache_l1[i].tag == dram_tag && cache_l1[i].valid){
			hitflag = true;
			runtime += 2;
			break;
		}
	}
	if(hitflag)	memcpy(cache_l1[i].data + offset, &data, len);
	else runtime += 200;
	cache_l2_write(addr, len, data);
}

void cache_l2_write(hwaddr_t addr, size_t len, uint32_t data){
	uint32_t group_num2 = (addr >> 6) & 0xfff;
	uint32_t offset2 = addr & 0x3f;
	int i;
	bool hitflag = false;
	for (i = group_num2 * SET_WAY_L2 ; i < (group_num2 + 1) * SET_WAY_L2 ; i ++){
		if (cache_l2[i].tag == (addr >> 13) && cache_l2[i].valid){
			hitflag = true;
			runtime += 2;
			break;
		}
	}
	if (!hitflag){
		i = cache_l2_read(addr);
		runtime += 200;
	}
	cache_l2[i].dirty = true;
	memcpy (cache_l2[i].data + offset2, &data ,len);
}

/* Memory accessing interfaces */

uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
	uint32_t offset = addr & 0x3f;
	uint32_t block = cache_read(addr);
	uint8_t temp[4];
	uint32_t *p = (uint32_t *)temp;
	memset(temp, 0, sizeof(temp));
	if (offset + len >= CACHE_BLOCK){
		uint32_t block_next = cache_read(addr + len);
		memcpy(temp, cache_l1[block].data + offset, CACHE_BLOCK - offset);
		memcpy(temp + CACHE_BLOCK - offset, cache_l1[block_next].data, len - (CACHE_BLOCK - offset));
	}
	else
		memcpy(temp, cache_l1[block].data + offset, len);
	return unalign_rw(p, 4) & (~0u >> ((4 - len) << 3)); 
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
	cache_write(addr, len, data);
}

hwaddr_t page_translate(lnaddr_t addr) {
	if(cpu.cr0.protect_enable && cpu.cr0.paging) {
		hwaddr_t tmpaddr;
		//printf("!");
		if ((tmpaddr = TLB_READ(addr & 0xfffff000)) != -1){
			return (tmpaddr << 12) + (addr & 0xfff);
		}
		
		PageEntry dir, page;
		uint32_t dir_offset = addr>>22;
		uint32_t page_offset = ((addr>>12) & 0x3ff);
		uint32_t offset = addr & 0xfff;
		dir.val = hwaddr_read((cpu.cr3.page_directory_base << 12) + (dir_offset << 2), 4);
		Assert(dir.p, "Invalid Page!");
		page.val = hwaddr_read((dir.base << 12) + (page_offset << 2), 4);
		Assert(page.p, "Invalid Page!");
		TLB_WRITE(addr & 0xfffff000, page.base);
		return (page.base << 12)+offset;
	} else {
	//	printf("?");
		return addr;
	}
}

uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
	size_t max_len = ((~addr) & 0xfff) + 1;
    if (len > max_len){
		uint32_t low = lnaddr_read(addr, max_len);
        uint32_t high = lnaddr_read(addr + max_len, len - max_len);
        return (high << (max_len << 3)) | low;
	}
	hwaddr_t hwaddr = page_translate(addr);
	return hwaddr_read(hwaddr, len);
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
	size_t max_len = ((~addr) & 0xfff) + 1;
    if (len > max_len){
        lnaddr_write(addr, max_len, data & ((1 << (max_len << 3)) - 1));
        lnaddr_write(addr + max_len, len - max_len, data >> (max_len << 3));
        return;
	}
	hwaddr_t hwaddr = page_translate(addr);
	hwaddr_write(hwaddr, len, data);
}

lnaddr_t seg_translate(swaddr_t addr, size_t len,uint8_t sreg){
	if(cpu.cr0.protect_enable == 0) return addr;
	//printf("SEGING!");
	return cpu.sreg[sreg].base + addr;
}

uint32_t swaddr_read(swaddr_t addr, size_t len,uint8_t sreg) {
//#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
//#endif
	lnaddr_t lnaddr = seg_translate(addr,len,sreg);
	return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data, uint8_t sreg) {
//#ifdef DEBUG
	assert(len == 1 || len == 2 || len == 4);
//#endif
	lnaddr_t lnaddr = seg_translate(addr,len,sreg);
	lnaddr_write(lnaddr, len, data);
}